﻿
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Peer
{


    public class PeerServer : IDisposable
    {
        private readonly Socket socketServer;
        private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        private readonly TaskFactory taskFactory;
        private readonly byte[] cipher;
        private readonly PeerOptions options;

        public PeerServer(string cipher, int port) : this(cipher, new IPEndPoint(IPAddress.Any, port), PeerOptions.Default)
        {

        }

        public PeerServer(string cipher, EndPoint endPoint) : this(cipher, endPoint, PeerOptions.Default)
        {

        }

        public PeerServer(string cipher, EndPoint endPoint, PeerOptions options)
        {
            this.options = options;
            this.cipher = Encoding.ASCII.GetBytes(cipher);
            socketServer = new Socket(options.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            socketServer.Bind(endPoint);
            taskFactory = new TaskFactory(cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskContinuationOptions.PreferFairness, TaskScheduler.Default);

        }

        public event EventHandler<PeerStream> OnAccept;

        public string Listen(int backlog = 10)
        {
            return Listen(backlog, CancellationToken.None);
        }

        public string Listen(int backlog, CancellationToken cancellationToken)
        {
            socketServer.Listen(backlog);
            Task.Factory.StartNew(() =>
            {
                while (!cancellationTokenSource.IsCancellationRequested)
                {
                    var client = socketServer.Accept();
                    taskFactory.StartNew(AcceptAsync, client)
                        .ContinueWith(e =>
                        {
                            client.Close();
                        });
                }

            }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
            return socketServer.LocalEndPoint.ToString();
        }

        private void AcceptAsync(object state)
        {
            var socketClient = state as Socket;
            var handshake = AcceptHandshake(socketClient);
            var id = handshake.Id;
            socketClient.ReceiveTimeout = 0;
            socketClient.Send(Handshake.NewHandshake(cipher, id).Bytes);
            using var peerStream = new PeerStream(id, socketClient, options.PeerConverter);
            try
            {
                OnAccept?.Invoke(this, peerStream);
                peerStream.ReceiveAsync();
            }
            catch (SocketException e)
            {

            }
            catch (Exception e)
            {
                peerStream.CallError(e);
            }
        }
        private Handshake AcceptHandshake(Socket socketClient)
        {
            socketClient.ReceiveTimeout = 2000;
            var headers = new byte[Handshake.HANDSHAKE_LENGTH];
            var length = socketClient.Receive(headers);
            var handshake = Handshake.Parse(headers, length);
            if (!handshake.Verify(cipher))
            {
                throw new SocketException((int)SocketError.InvalidArgument);
            }
            return handshake;

        }

        public void Dispose()
        {
            cancellationTokenSource.Cancel();
            socketServer.Shutdown(SocketShutdown.Both);
            socketServer.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}
